/*
 * Copyright 2010 - 2011, The PLDesktop Development Team
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 */
#ifndef PLRINGLIST_H
#define PLRINGLIST_H

#include <tools/PLList.h>
#include <tools/PLLinkedListNode.h>
#include <tools/PLILinkedListIterator.h>
#include <tools/PLLinkedListIterator.h>

namespace PLSDK {
    
    /**
     * Ringlist.
     */
    template <typename T>
    class PLRingList : public PLList<T>, public PLILinkedListIterator<T> {
    public:
        /**
         * Constructor.
         */
        PLRingList();
        /**
         * Destructor.
         */
        ~PLRingList();
        /**
         * Add a new node to the list with the object object.
         *
         * @param object The object to add to the list.
         * @param current The current object after that we add the item.
         */
        inline virtual void add(T object, PLLinkedListNode<T> *current = 0);
        /**
         * Append a new object.
         *
         * @param object Object to append.
         */
        inline virtual void append(T object);
        /**
         * Prepend a new object.
         *
         * @param object Object to prepend.
         */
        inline virtual void prepend(T object);
        /**
         * The number of objects in the list.
         *
         * @return The number of objects in the list.
         */
        inline pluint64 count();
        /**
         * Delete a object.
         *
         * @param current The object to remove.
         */
        inline PLLinkedListNode<T> *remove(PLLinkedListNode<T> *current);
        /**
         * Delete a object.
         *
         * @param object Object to delete from the list.
         * @return True if the object was deleted from the list.
         * If no object exists it returns false.
         */
        inline virtual plbool removeAll(T object);
        /**
         * Set a object to the current object.
         *
         * @param object The data to set.
         * @param current The current node.
         */
        inline void set(T object, PLLinkedListNode<T> *current);
        /**
         * Have the current node a next object.
         *
         * @param current The current node.
         * @return true If the node have a next node.
         */
        inline bool hasNext(PLLinkedListNode<T> *current);
        /**
         * Have the current node a previous node.
         *
         * @param current The current node.
         * @return true If the node have a previous node.
         */
        inline bool hasPrevious(PLLinkedListNode<T> *current);
        /**
         * Get the next object.
         *
         * @param current The current node.
         * @return The object and go to the next object.
         */
        inline T next(PLLinkedListNode<T> *current);
        /**
         * Get the previous object.
         *
         * @param current The current node.
         * @return The object and go to the previous object.
         */
        inline T previous(PLLinkedListNode<T> *current);
        /**
         * Is the current node the first node.
         *
         * @param current The current node.
         * @return True if the current node is the first node.
         */
        inline bool isFirstNode(PLLinkedListNode<T> *current);
        /**
         * Contains the list a object.
         *
         * @param object The object to test for.
         * @return True if the list contains the object.
         */
        inline bool contains(T object);
        /**
         * Get the hash value of the object.
         *
         * @return The hash value of the object. The hash for the PLObject object was 1.
         */
        inline virtual pluint hash() const;
        /**
         * Get a new iterator for the list.
         *
         * @return The iterator for the list.
         */
        inline PLLinkedListIterator<T> iterator();
        /**
         * Compare to objects.
         *
         * @param object Object to compare with.
         * @return The result of the compare.
         * @todo implement
         */
        inline virtual PLCompareResult compareTo(PLObject *object);
        /**
         * Get the last node of the list.
         *
         * @return The last node.
         */
        inline virtual PLListNode<T> &lastNode();
    private:
        PLDISABLE_COPY(PLRingList<T>)
    };

    template<typename T>
    PLRingList<T>::PLRingList() : PLList<T>() {
    }

    template<typename T>
    PLRingList<T>::~PLRingList() {
        if(PLList<T>::m_first != 0) {
            PLListNode<T> *n = PLList<T>::m_first->next();
            if(n != 0) {
                static_cast<PLLinkedListNode<T> *>(n)->previous()->setNextNode(0);
            }
        }
    }

    template<typename T>
    pluint64 PLRingList<T>::count() {
        if(PLList<T>::m_first->next() == 0) {
            return 0;
        }
        pluint64 countValue = 0;
        PLLinkedListIterator<T> it = iterator();
        while(it != lastNode()) {
            countValue++;
            it.next();
        }
        return countValue;
    }

    template<typename T>
    void PLRingList<T>::add(T object, PLLinkedListNode<T> *current) {
        PLLinkedListNode<T> *_tempNode = new PLLinkedListNode<T>(object);
        if(current != 0) {
            _tempNode->setNextNode(current->next());
            _tempNode->setPreviousNode(current);
            static_cast<PLLinkedListNode<T> *>(current->next())->setPreviousNode(_tempNode);
            current->setNextNode(_tempNode);
        } else {
            if(PLList<T>::m_first->next() != 0) {
                PLLinkedListNode<T> *_first = static_cast<PLLinkedListNode<T> *>(PLList<T>::m_first->next());
                _tempNode->setNextNode(_first);
                _tempNode->setPreviousNode(static_cast<PLLinkedListNode<T> *>(_first)->previous());
                _first->previous()->setNextNode(_tempNode);
                _first->setPreviousNode(_tempNode);
            } else {
                _tempNode->setNextNode(_tempNode);
                _tempNode->setPreviousNode(_tempNode);
                PLList<T>::m_first->setNextNode(_tempNode);
            }
        }
    }

    template<typename T>
    void PLRingList<T>::append(T object) {
        add(object);
    }

    template<typename T>
    void PLRingList<T>::prepend(T object) {
        add(object, static_cast<PLLinkedListNode<T> *>(PLList<T>::m_first->next())->previous());
    }

    template<typename T>
    bool PLRingList<T>::hasNext(PLLinkedListNode<T> *current) {
        if(current != 0 && current->next() != 0) {
            return true;
        }
        return false;
    }

    template<typename T>
    bool PLRingList<T>::hasPrevious(PLLinkedListNode<T> *) {
        if(PLList<T>::m_first->next() != 0) {
            return true;
        }
        return false;
    }

    template<typename T>
    T PLRingList<T>::next(PLLinkedListNode<T> *current) {
        if(hasNext(current)) {
            return current->next()->content();
        }
        return T();
    }

    template<typename T>
    T PLRingList<T>::previous(PLLinkedListNode<T> *current) {
        if(hasPrevious(current)) {
            return current->previous()->content();
        }
        return T();
    }

    template<typename T>
    bool PLRingList<T>::isFirstNode(PLLinkedListNode<T> *current) {
        return static_cast<PLLinkedListNode<T> *>(PLList<T>::m_first->next()) == current;
    }

    template<typename T>
    bool PLRingList<T>::contains(T object) {
        T value;
        PLLinkedListIterator<T> it = iterator();
        while(it != lastNode()) {
            value = it.next();
            if(value == object) {
                return true;
            }
        }
        return false;
    }

    template<typename T>
    pluint PLRingList<T>::hash() const {
        return 2;
    }

    template<typename T>
    PLLinkedListIterator<T> PLRingList<T>::iterator() {
        return PLLinkedListIterator<T>(this, static_cast<PLLinkedListNode<T> *>(PLList<T>::m_first));
    }

    template<typename T>
    PLCompareResult PLRingList<T>::compareTo(PLObject *object) {
        PLUNUSED(object)
        return PLCompareResultSame;
    }

    template<typename T>
    PLListNode<T> &PLRingList<T>::lastNode() {
        return *static_cast<PLLinkedListNode<T> *>(PLList<T>::m_first->next())->previous();
    }

    template<typename T>
    PLLinkedListNode<T> *PLRingList<T>::remove(PLLinkedListNode<T> *current) {
        if(current != 0) {
            PLLinkedListNode<T> *_tempNode = static_cast<PLLinkedListNode<T> *>(PLList<T>::m_first);
            PLLinkedListNode<T> *ret = static_cast<PLLinkedListNode<T> *>(_tempNode->next());
            while(ret != 0 && *ret != *current) {
                _tempNode = static_cast<PLLinkedListNode<T> *>(ret);
                ret = static_cast<PLLinkedListNode<T> *>(_tempNode->next());
            }
            if(*ret == *current) {
                ret = static_cast<PLLinkedListNode<T> *>(current->next());
                _tempNode->setNextNode(ret);
                ret->setPreviousNode(_tempNode);
                delete current;
                return ret;
            }
        }
        return 0;
    }

    template<typename T>
    plbool PLRingList<T>::removeAll(T object) {
        plbool ret = false;
        PLLinkedListNode<T> *_firstNode = static_cast<PLLinkedListNode<T> *>(PLList<T>::m_first->next());
        PLLinkedListNode<T> *_tempNode = _firstNode;
        if(_firstNode != 0) {
            if(_firstNode->next() != 0) {
                _tempNode = static_cast<PLLinkedListNode<T> *>(_tempNode->next());
                while(_tempNode->next() != 0 && _tempNode != _firstNode) {
                    if(_tempNode->content() == object) {
                        _tempNode->previous()->setNextNode(_tempNode->next());
                        if(_tempNode->next() != 0) {
                            static_cast<PLLinkedListNode<T> *>(_tempNode->next())->setPreviousNode(_tempNode->previous());
                        }
                        delete _tempNode;
                        ret = true;
                    }
                    _tempNode = static_cast<PLLinkedListNode<T> *>(_tempNode->next());
                }
            } else {
                if(_tempNode->content() == object) {
                    delete _firstNode;
                    PLList<T>::m_first->setNextNode(0);
                    ret = true;
                }
            }
        }
        return ret;
    }

    template<typename T>
    void PLRingList<T>::set(T object, PLLinkedListNode<T> *current) {
        if(current != 0) {
            current->setContent(object);
        }
    }

}

#endif
